package math;

public class vec3
{
    
    public static final vec3 ZERO = new vec3();
    public static final vec3 HALF = new vec3(0.5);
    public static final vec3 ONE = new vec3(1);
    
    public static final vec3 AXIS_XP = new vec3(1, 0, 0);
    public static final vec3 AXIS_YP = new vec3(0, 1, 0);
    public static final vec3 AXIS_ZP = new vec3(0, 0, 1);
    
    public static final vec3 AXIS_XN = new vec3(-1,  0,  0);
    public static final vec3 AXIS_YN = new vec3( 0, -1,  0);
    public static final vec3 AXIS_ZN = new vec3( 0,  0, -1);
    
    
    public double x;
    
    public double y;
    
    public double z;
    
    
    public vec3()
    {
        this.set(0, 0, 0);
    }
    
    public vec3(double aXYZ)
    {
        this.set(aXYZ);
    }
    
    public vec3(
            double aX, 
            double aY, 
            double aZ)
    {
        this.set(aX, aY, aZ);
    }
    
    public vec3(
            vec2 aXY, 
            double aZ)
    {
        this.set(aXY.x, aXY.y, aZ);
    }
    
    public vec3(vec3i aXYZi)
    {
        this.set(aXYZi);
    }
    
    public vec3(vec4 aXYZW)
    {
        this.set(aXYZW.x, aXYZW.y, aXYZW.z);
    }
    
    
    public final void set(double aXYZ)
    {
        this.set(aXYZ, aXYZ, aXYZ);
    }
    
    public final void set(
            double aX, 
            double aY, 
            double aZ)
    {
        this.x = aX;
        this.y = aY;
        this.z = aZ;
    }
    
    public final void set(vec3 aXYZ)
    {
        this.set(aXYZ.x, aXYZ.y, aXYZ.z);
    }
    
    public final void set(vec3i aXYZi)
    {
        this.set(aXYZi.x, aXYZi.y, aXYZi.z);
    }
    
    
    public void add(vec3 aTransl)
    {
        this.x += aTransl.x;
        this.y += aTransl.y;
        this.z += aTransl.z;
    }
    
    public void add(double aTransl)
    {
        this.x += aTransl;
        this.y += aTransl;
        this.z += aTransl;
    }
    
    public void sub(vec3 aTransl)
    {
        this.x -= aTransl.x;
        this.y -= aTransl.y;
        this.z -= aTransl.z;
    }
    
    public void sub(double aTransl)
    {
        this.x -= aTransl;
        this.y -= aTransl;
        this.z -= aTransl;
    }
    
    public void mul(vec3 aScale)
    {
        this.x *= aScale.x;
        this.y *= aScale.y;
        this.z *= aScale.z;
    }
    
    public void mul(double aScale)
    {
        this.x *= aScale;
        this.y *= aScale;
        this.z *= aScale;
    }
    
    public void div(vec3 aScale)
    {
        this.x /= aScale.x;
        this.y /= aScale.y;
        this.z /= aScale.z;
    }
    
    public void div(double aScale)
    {
        double rScale = 1.0 / aScale;
        this.mul(rScale);
    }
    
    public void addmul(vec3 aFac0, vec3 aFac1)
    {
        this.x += aFac0.x * aFac1.x;
        this.y += aFac0.y * aFac1.y;
        this.z += aFac0.z * aFac1.z;
    }
    
    public void addmul(vec3 aFac0, double aFac1)
    {
        this.x += aFac0.x * aFac1;
        this.y += aFac0.y * aFac1;
        this.z += aFac0.z * aFac1;
    }
    
    public void addmul(double aFac0, vec3 aFac1)
    {
        this.addmul(aFac1, aFac0);
    }
    
    public void pow(vec3 aExp)
    {
        this.x = math.powd(this.x, aExp.x);
        this.y = math.powd(this.y, aExp.y);
        this.z = math.powd(this.z, aExp.z);
    }
    
    public void pow(double aExp)
    {
        this.x = math.powd(this.x, aExp);
        this.y = math.powd(this.y, aExp);
        this.z = math.powd(this.z, aExp);
    }
    
    public void abs()
    {
        this.x = math.abs(this.x);
        this.y = math.abs(this.y);
        this.z = math.abs(this.z);
    }
    
    public void clamp(vec3 aBoundsLo, vec3 aBoundsHi)
    {
        this.x = math.clampd(this.x, aBoundsLo.x, aBoundsHi.x);
        this.y = math.clampd(this.y, aBoundsLo.y, aBoundsHi.y);
        this.z = math.clampd(this.z, aBoundsLo.z, aBoundsHi.z);
    }
    
    public void clamp(double aBoundsLo, double aBoundsHi)
    {
        this.x = math.clampd(this.x, aBoundsLo, aBoundsHi);
        this.y = math.clampd(this.y, aBoundsLo, aBoundsHi);
        this.z = math.clampd(this.z, aBoundsLo, aBoundsHi);
    }
    
    public void norm()
    {
        this.mul(this.rlength());
    }
    
    public void lerp(vec3 aOther, double aInterpTerm)
    {
        this.x = math.lerp(this.x, aOther.x, aInterpTerm);
        this.y = math.lerp(this.y, aOther.y, aInterpTerm);
        this.z = math.lerp(this.z, aOther.z, aInterpTerm);
    }
    
    public void proj(vec3 aBasis)
    {
        double projFrac = this.projFraction(aBasis);
        this.set(aBasis);
        this.mul(projFrac);
    }
    
    
    public boolean close(vec3 aOther, double aError)
    {
        return (
            math.abs(this.x - aOther.x) < aError && 
            math.abs(this.y - aOther.y) < aError && 
            math.abs(this.z - aOther.z) < aError
        );
    }
    
    public boolean lessAll(vec3 aOther)
    {
        return (
            this.x < aOther.x && 
            this.y < aOther.y && 
            this.z < aOther.z
        );
    }
    
    public boolean lessAny(vec3 aOther)
    {
        return (
            this.x < aOther.x || 
            this.y < aOther.y || 
            this.z < aOther.z
        );
    }
    
    public boolean lequalAll(vec3 aOther)
    {
        return (
            this.x <= aOther.x && 
            this.y <= aOther.y && 
            this.z <= aOther.z
        );
    }
    
    public boolean lequalAny(vec3 aOther)
    {
        return (
            this.x <= aOther.x || 
            this.y <= aOther.y || 
            this.z <= aOther.z
        );
    }
    
    public boolean greaterAll(vec3 aOther)
    {
        return (
            this.x > aOther.x && 
            this.y > aOther.y && 
            this.z > aOther.z
        );
    }
    
    public boolean greaterAny(vec3 aOther)
    {
        return (
            this.x > aOther.x || 
            this.y > aOther.y || 
            this.z > aOther.z
        );
    }
    
    public boolean gequalAll(vec3 aOther)
    {
        return (
            this.x >= aOther.x && 
            this.y >= aOther.y && 
            this.z >= aOther.z
        );
    }
    
    public boolean gequalAny(vec3 aOther)
    {
        return (
            this.x >= aOther.x || 
            this.y >= aOther.y || 
            this.z >= aOther.z
        );
    }
    
    
    public double dot(vec3 aOther)
    {
        return (
            this.x * aOther.x + 
            this.y * aOther.y + 
            this.z * aOther.z
        );
    }
    
    public double lengthSq()
    {
        return this.dot(this);
    }
    
    public double length()
    {
        return math.sqrt(this.dot(this));
    }
    
    public double rlength()
    {
        return math.rsqrt(this.dot(this));
    }
    
    public double projComponent(vec3 aBasis)
    {
        return this.dot(aBasis) * aBasis.rlength();
    }
    
    public double projFraction(vec3 aBasis)
    {
        return this.dot(aBasis) / aBasis.dot(aBasis);
    }
    
    
    public vec3 copy()
    {
        vec3 copy = new vec3();
        copy.set(this);
        return copy;
    }
    
    
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append("( ");
        sb.append(String.format("%.2f", this.x));
        sb.append(", ");
        sb.append(String.format("%.2f", this.y));
        sb.append(", ");
        sb.append(String.format("%.2f", this.z));
        sb.append(" )");
        return sb.toString();
    }
    
    
    
    public static vec3 add(vec3 aA, vec3 aB)
    {
        vec3 add = aA.copy();
        add.add(aB);
        return add;
    }
    
    public static vec3 add(vec3 aA, double aB)
    {
        vec3 add = aA.copy();
        add.add(aB);
        return add;
    }
    
    public static vec3 add(double aA, vec3 aB)
    {
        vec3 add = new vec3(aA);
        add.add(aB);
        return add;
    }
    
    public static vec3 add(double aA, double aB)
    {
        double add = aA + aB;
        return new vec3(add);
    }
    
    public static vec3 sub(vec3 aA, vec3 aB)
    {
        vec3 sub = aA.copy();
        sub.sub(aB);
        return sub;
    }
    
    public static vec3 sub(vec3 aA, double aB)
    {
        vec3 sub = aA.copy();
        sub.sub(aB);
        return sub;
    }
    
    public static vec3 sub(double aA, vec3 aB)
    {
        vec3 sub = new vec3(aA);
        sub.sub(aB);
        return sub;
    }
    
    public static vec3 sub(double aA, double aB)
    {
        double sub = aA - aB;
        return new vec3(sub);
    }
    
    public static vec3 mul(vec3 aA, vec3 aB)
    {
        vec3 mul = aA.copy();
        mul.mul(aB);
        return mul;
    }
    
    public static vec3 mul(vec3 aA, double aB)
    {
        vec3 mul = aA.copy();
        mul.mul(aB);
        return mul;
    }
    
    public static vec3 mul(double aA, vec3 aB)
    {
        vec3 mul = new vec3(aA);
        mul.mul(aB);
        return mul;
    }
    
    public static vec3 mul(double aA, double aB)
    {
        double mul = aA * aB;
        return new vec3(mul);
    }
    
    public static vec3 div(vec3 aA, vec3 aB)
    {
        vec3 div = aA.copy();
        div.div(aB);
        return div;
    }
    
    public static vec3 div(vec3 aA, double aB)
    {
        vec3 div = aA.copy();
        div.div(aB);
        return div;
    }
    
    public static vec3 div(double aA, vec3 aB)
    {
        vec3 div = new vec3(aA);
        div.div(aB);
        return div;
    }
    
    public static vec3 div(double aA, double aB)
    {
        double div = aA / aB;
        return new vec3(div);
    }
    
    public static vec3 pow(vec3 aVec, vec3 aExp)
    {
        vec3 pow = aVec.copy();
        pow.pow(aExp);
        return pow;
    }
    
    public static vec3 pow(vec3 aVec, double aExp)
    {
        vec3 pow = aVec.copy();
        pow.pow(aExp);
        return pow;
    }
    
    public static vec3 abs(vec3 aVec)
    {
        vec3 abs = aVec.copy();
        abs.abs();
        return abs;
    }
    
    public static vec3 clamp(vec3 aVec, vec3 aBoundsLo, vec3 aBoundsHi)
    {
        vec3 clamp = aVec.copy();
        clamp.clamp(aBoundsLo, aBoundsHi);
        return clamp;
    }
    
    public static vec3 clamp(vec3 aVec, double aBoundsLo, double aBoundsHi)
    {
        vec3 clamp = aVec.copy();
        clamp.clamp(aBoundsLo, aBoundsHi);
        return clamp;
    }
    
    public static vec3 norm(vec3 aVec)
    {
        vec3 norm = aVec.copy();
        norm.norm();
        return norm;
    }
    
    public static vec3 lerp(vec3 aA, vec3 aB, double aInterpTerm)
    {
        vec3 lerp = aA.copy();
        lerp.lerp(aB, aInterpTerm);
        return lerp;
    }
    
    public static vec3 proj(vec3 aVec, vec3 aBasis)
    {
        vec3 proj = aVec.copy();
        proj.proj(aBasis);
        return proj;
    }
    
    public static vec3 perp(vec3 aVec)
    {
        vec3 other = aVec.copy();
        other.x += 17.0;
        other.y -= 2.0;
        other.z *= 1.767;
        vec3 perp = vec3.cross(aVec, other);
        perp.norm();
        perp.mul(aVec.length());
        return perp;
    }
    
    public static vec3 cross(vec3 aA, vec3 aB)
    {
        return new vec3(
            aA.y * aB.z - aA.z * aB.y, 
            aA.z * aB.x - aA.x * aB.z, 
            aA.x * aB.y - aA.y * aB.x
        );
    }
    
    
    
}
